package com.lenovo.lcdm.dcm.common;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import com.google.common.collect.Maps;
import com.lenovo.lcdm.common.enums.CommonMsgEnum;
import com.lenovo.lcdm.common.exception.ServiceException;
import com.lenovo.lcdm.dcm.common.util.Checker;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * @author sunby1
 */
@Slf4j
@Configuration
@ConditionalOnExpression("!''.equals('${plm.aws.s3.bucket}')")
@Component
public class RemoteObjectStorageSupport implements DisposableBean {

    private AmazonS3 s3Client;
    private AmazonS3 httpsS3Client;

    @Value("${plm.aws.s3.ak:}")
    private String ak01;
    @Value("${plm.aws.s3.sk:}")
    private String sk01;
    @Value("${plm.aws.s3.bucket:}")
    private String bucketName;
    @Value("${plm.aws.s3.endpoint:}")
    private String endPoint;
    @Value("${plm.aws.s3.https-endpoint:}")
    private String httpsEndpoint;
    @Value("${plm.aws.s3.jks.store-path:}")
    private String jksStorePath;
    @Value("${plm.aws.s3.jks.pwd:}")
    private String jksPwd;

    @PostConstruct
    public void init() {
        if (StrUtil.isNotBlank(bucketName)) {
            s3Client = AmazonS3ClientBuilder.standard()
                    .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(ak01, sk01)))
                    .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, Regions.CN_NORTH_1.getName()))
                    .enablePathStyleAccess().build();

            httpsS3Client = s3Client;
        }
    }

    @Override
    public void destroy() throws Exception {
        s3Client.shutdown();
        httpsS3Client.shutdown();
    }

    public void putObject(String objectId, InputStream inputStream, long contentLength) {
        ObjectMetadata om = new ObjectMetadata();
        om.setContentLength(contentLength);
        if (!s3Client.doesBucketExistV2(bucketName)) {
            s3Client.createBucket(new CreateBucketRequest(bucketName));
        }
        PutObjectRequest request = new PutObjectRequest(bucketName, objectId, inputStream, om);
        try {
            s3Client.putObject(request);
        } catch (Exception e) {
            log.warn("RemoteObjectStorageSupport putObject failed, objectId:{}, exp:{}", objectId, e);
            throw new ServiceException(CommonMsgEnum.UPLOAD_FILE_FAILED);
        }
    }

    public void doWithObjectInputStream(String objectId, Consumer<InputStream> inputStreamConsumer) {
        Checker.notEmpty(objectId, CommonMsgEnum.PARAMETER_ERROR);
        Checker.notEmpty(inputStreamConsumer, CommonMsgEnum.PARAMETER_ERROR);
        try (S3Object s3Object = s3Client.getObject(bucketName, objectId);
             S3ObjectInputStream inputStream = s3Object.getObjectContent()) {

            inputStreamConsumer.accept(inputStream);
        } catch (Exception e) {
            log.warn("Get Remote Object Failed, objectId:{}, exp:{}", objectId, e);
            throw new ServiceException(CommonMsgEnum.GET_FILE_FAILED);
        }
    }

    /**
     * 调用方关闭InputStream
     */
    public InputStream getObjectInputStream(String objectId) {
        Checker.notEmpty(objectId, CommonMsgEnum.PARAMETER_ERROR);
        try {
            S3Object s3Object = s3Client.getObject(bucketName, objectId);
            return s3Object.getObjectContent();
        } catch (AmazonServiceException e) {
            log.warn("Get Remote Object Failed, objectId:{}, exp:{}", objectId, e);
            throw new ServiceException(CommonMsgEnum.GET_FILE_FAILED);
        }
    }

    public Map<String, InputStream> getObjectInputStream(Collection<String> objectIds) {
        Checker.notEmpty(objectIds, CommonMsgEnum.PARAMETER_ERROR);
        Map<String, InputStream> result = Maps.newHashMapWithExpectedSize(objectIds.size());
        try {
            for (String objectId : objectIds) {
                S3Object s3Object = s3Client.getObject(bucketName, objectId);
                result.put(objectId, s3Object.getObjectContent());
            }
        } catch (AmazonServiceException e) {
            log.warn("Get Remote Object Failed, objectIds: {}, exp:{}", objectIds, e);
            result.values().forEach(inputStream -> {
                try {
                    inputStream.close();
                } catch (Exception e2) {
                    log.warn("getObjectInputStream close input stream failed, exp:{}", e2);
                }
            });

            throw new ServiceException(CommonMsgEnum.GET_FILE_FAILED);
        }

        return result;
    }

    public void deleteObject(Collection<String> objectIds, boolean ignoreExp) {
        if (CollUtil.isEmpty(objectIds)) {
            return;
        }

        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
        deleteObjectsRequest.setKeys(objectIds.stream().map(DeleteObjectsRequest.KeyVersion::new).toList());
        deleteObjectsRequest.setQuiet(false);
        deleteObjectsRequest.setSdkRequestTimeout((int) TimeUnit.SECONDS.toMillis(3L));

        try {
            s3Client.deleteObjects(deleteObjectsRequest);
        } catch (Exception e) {
            log.warn("deleteObject failed, objectIds:{}, err:{}", objectIds, e);
            if (!ignoreExp) {
                throw new ServiceException(CommonMsgEnum.DELETE_FILE_FAILED);
            }
        }
    }

    public String getPreSignedUrl(String fileKey, String fileName, HttpMethod method) {
        ResponseHeaderOverrides ro = new ResponseHeaderOverrides();
        ro.setContentDisposition("attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
        GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, fileKey)
                .withMethod(method)
                .withExpiration(DateUtil.offsetDay(new Date(), 3))
                .withResponseHeaders(ro);
        return httpsS3Client.generatePresignedUrl(generatePresignedUrlRequest).toString();
    }

    public boolean fileExist(String fileKey) {
        return s3Client.doesObjectExist(bucketName, fileKey);
    }
}
